CoMarshalInterface vraci E_INVALIDARG

Otázka od: Karel Kimes

19. 9. 2002 16:40

ahoj,
prosim projdete si nize uvedeny kod metody COM komponenty.



procedure TKCH_DCOM_03.Go(delay: Integer);
var
   _IStream : IStream;
   _IStream_h : THandle;
   res : HRESULT;
begin
   res := CreateStreamOnHGlobal(0, false, _IStream);
   if res <> S_OK then
     raise Exception.Create('CreateStreamOnHGlobal error: ' + IntToStr(res));

   res := GetHGlobalFromStream(_IStream, _IStream_h);
   if res <> S_OK then
     raise Exception.Create('GetHGlobalFromStream error: ' + IntToStr(res));


   res := CoMarshalInterface(_IStream, DIID_IKCH_DCOM_03Events, FEvents,
MSHCTX_DIFFERENTMACHINE, nil, MSHLFLAGS_NORMAL);
// ***** pri pouziti komponenty na jednom PC je vse OK, ALE pokud se snazim
volat komponentu na jinem PC (DCOM je nastaveno dobre, vcetne
opravneni!!!), tak mne to vraci E_INVALIDARG. O co gou???



   if res <> S_OK then
     raise Exception.Create('CoMarshalInterface error: ' + IntToStr(res));

   _IStream := nil;
// **** uvolnim timto _IStream??? nebo musim volat _Release???


   TTestThread.Create(delay, _IStream_h); // ve vlakne provadim
CoUnmarshallInterface

   CreateLOG('d:\dcom\_GoEnd.txt');
end;


kchodl

WinXP, D6PRO UPDATE PACK 2

Odpovedá: Malecek Ondrej

19. 9. 2002 16:35

Kde se vzalo FEvents ve volani CoMarshalInterface ??? Takhle z toho nejde
nic moc vykoukat  

O  

> -----Původní zpráva-----
> ahoj,
> prosim projdete si nize uvedeny kod metody COM komponenty.
>
>
>
> procedure TKCH_DCOM_03.Go(delay: Integer);
> var
> _IStream : IStream;
> _IStream_h : THandle;
> res : HRESULT;
> begin
> res := CreateStreamOnHGlobal(0, false, _IStream);
> if res <> S_OK then
> raise Exception.Create('CreateStreamOnHGlobal error: ' +
> IntToStr(res));
>
> res := GetHGlobalFromStream(_IStream, _IStream_h);
> if res <> S_OK then
> raise Exception.Create('GetHGlobalFromStream error: ' +
> IntToStr(res));
>
>
> res := CoMarshalInterface(_IStream, DIID_IKCH_DCOM_03Events, FEvents,
> MSHCTX_DIFFERENTMACHINE, nil, MSHLFLAGS_NORMAL);
> // ***** pri pouziti komponenty na jednom PC je vse OK, ALE pokud
> se snazim
> volat komponentu na jinem PC (DCOM je nastaveno dobre, vcetne
> opravneni!!!), tak mne to vraci E_INVALIDARG. O co gou???
>
>
>
> if res <> S_OK then
> raise Exception.Create('CoMarshalInterface error: ' + IntToStr(res));
>
> _IStream := nil;
> // **** uvolnim timto _IStream??? nebo musim volat _Release???
>
>
> TTestThread.Create(delay, _IStream_h); // ve vlakne provadim
> CoUnmarshallInterface
>
> CreateLOG('d:\dcom\_GoEnd.txt');
> end;
>
>
> kchodl
>
> WinXP, D6PRO UPDATE PACK 2
>

Odpovedá: Karel Kimes

23. 9. 2002 9:25


>Kde se vzalo FEvents ve volani CoMarshalInterface ???

FEvents je private atribut tridy TKCH_DCOM_03, ktery byl vytvoren pomoci
wizardu v D6 (na zaklade DAX) a obsahuje source interface, jehoz metody
musi klient implementovat v sink objektu. V tomto konkretnim pripade,
obsahuje, mimo jine i metodu OnEvent, kterou klient MUSI implementovat.
Tento interface se tedy snazim marshalovat. V pripade behu jako out-process
je vse OK. V pripade behu klienta a serveru na ruznych PC dojde ke zminene
chybe E_INVALIDARG pri volani CoMarshalInterface.



> > -----Původní zpráva-----
> > ahoj,
> > prosim projdete si nize uvedeny kod metody COM komponenty.
> >
> >
> >
> > procedure TKCH_DCOM_03.Go(delay: Integer);
> > var
> > _IStream : IStream;
> > _IStream_h : THandle;
> > res : HRESULT;
> > begin
> > res := CreateStreamOnHGlobal(0, false, _IStream);
> > if res <> S_OK then
> > raise Exception.Create('CreateStreamOnHGlobal error: ' +
> > IntToStr(res));
> >
> > res := GetHGlobalFromStream(_IStream, _IStream_h);
> > if res <> S_OK then
> > raise Exception.Create('GetHGlobalFromStream error: ' +
> > IntToStr(res));
> >
> >
> > res := CoMarshalInterface(_IStream, DIID_IKCH_DCOM_03Events, FEvents,
> > MSHCTX_DIFFERENTMACHINE, nil, MSHLFLAGS_NORMAL);
> > // ***** pri pouziti komponenty na jednom PC je vse OK, ALE pokud
> > se snazim
> > volat komponentu na jinem PC (DCOM je nastaveno dobre, vcetne
> > opravneni!!!), tak mne to vraci E_INVALIDARG. O co gou???
> >
> >
> >
> > if res <> S_OK then
> > raise Exception.Create('CoMarshalInterface error: ' + IntToStr(res));
> >
> > _IStream := nil;
> > // **** uvolnim timto _IStream??? nebo musim volat _Release???
> >
> >
> > TTestThread.Create(delay, _IStream_h); // ve vlakne provadim
> > CoUnmarshallInterface
> >
> > CreateLOG('d:\dcom\_GoEnd.txt');
> > end;
> >
> >
> > kchodl
> >
> > WinXP, D6PRO UPDATE PACK 2
> >

Odpovedá: Malecek Ondrej

23. 9. 2002 11:58

Hm, pokud mas ten interface vzity z TDispatch, neni nutno pri jeho predavani
do jineho
procesu marshallovat. Manualni marshalovani je nutne pouze pri predavani
mezi apartmenty uvnitr procesu. Mimo process se o to stara standardni
proxy-stub, akorat nutno pouzivat pouze automation compatible typy, ale to
asi vis.
Ale zpatky k tomu problemu. Nemuze to byt tim, ze na tech masinach nemas
registrovana vsechna pozuvana rozhrani. Mel jsem problemy s tim, ze pokud
jsem pouzival eventy mezi dvema PC, musel jsem mit rozhrani jak serveru, tak
i klienta registrovana na obou strojich, mam dojem, ze jsem nekde cetl o
souvislosti s tim, jak Delphi pracuji s typovymi knihovnami. Nemuze to byt
timhle ???

O  

> -----Puvodni zprava-----
> >Kde se vzalo FEvents ve volani CoMarshalInterface ???
>
> FEvents je private atribut tridy TKCH_DCOM_03, ktery byl vytvoren pomoci
> wizardu v D6 (na zaklade DAX) a obsahuje source interface, jehoz metody
> musi klient implementovat v sink objektu. V tomto konkretnim pripade,
> obsahuje, mimo jine i metodu OnEvent, kterou klient MUSI implementovat.
> Tento interface se tedy snazim marshalovat. V pripade behu jako
> out-process
> je vse OK. V pripade behu klienta a serveru na ruznych PC dojde
> ke zminene
> chybe E_INVALIDARG pri volani CoMarshalInterface.

Odpovedá: Karel Kimes

23. 9. 2002 16:45


>Hm, pokud mas ten interface vzity z TDispatch, neni nutno pri jeho predavani
>do jineho
>procesu marshallovat. Manualni marshalovani je nutne pouze pri predavani
>mezi apartmenty uvnitr procesu.
**** mas pravdu, uz sem to objevil   (pozde, ale prece - jedna se o MTA =>
no marshaling required)

>akorat nutno pouzivat pouze automation compatible typy, ale to
>asi vis.
**** vim


>Ale zpatky k tomu problemu. Nemuze to byt tim, ze na tech masinach nemas
>registrovana vsechna pozuvana rozhrani. Mel jsem problemy s tim, ze pokud
>jsem pouzival eventy mezi dvema PC, musel jsem mit rozhrani jak serveru, tak
>i klienta registrovana na obou strojich, mam dojem, ze jsem nekde cetl o
>souvislosti s tim, jak Delphi pracuji s typovymi knihovnami. Nemuze to byt
>timhle ???

**** ZPATKY NA STROMY (zapomenme na to, ze sem resil marshalovani, protoze
uz vim kde je chyba, ALE neznam PRICINU)!!! Viz. dale...

1) Mam Automation objekt, ktery byl generovan pres wizarda v D6PRO, s
podporou events.
2) Mam klienta, ktery si naimportoval typovou knihovnu Automation objektu,
jako komponentu.
3) klient vola metodu napr. Go (dojde ke spojeni s Automation objektem)
4) automation objekt v implementaci metody Go ma vyvolat udalost. Tedy
napr. FEvents.OnEvent (FEvent je soucasti CoClass objektu a generoval ji
wizard, je to source interface, ktery MUSI implementovat klient)

Definujme varianty:
A) klient je spusten na PC, kde take registrovan automation objekt.
B) klient je spusten na PC, kde je registrovan automation objekt jako
remote server a automation objekt bezi na tomto remote serveru, kde je take
registrovan. (pro nastaveni DCOM sem pouzil DCOMCNFG.EXE a na obou PC sem
nastavil pro tento vsechna prava atd. => v tom bych problem nehledal, ale
na 100% si jistej nejsu   )

U var. A je vse OK.
U var. B sem zjistil, ze je FEvents = NIL !!!
Mj. to je ten problem proc ten marshaling nefungoval (pokousel sem se
marshalovat interface, ktery byl NIL - coz asi nejde, ze   ).

No dobre, ale jak z toho ven. Pokracoval sem v pruzkumu a zameril sem se na
klienta, ktery by v urcitem bode mel serveru predat ukazatel na sink event
objekt a to procedurou InterfaceConnect, definovanou v jednotce ComObj.pas.
Ta procedura vypada takto:


procedure InterfaceConnect(const Source: IUnknown; const IID: TIID;
   const Sink: IUnknown; var Connection: Longint);
var
   CPC: IConnectionPointContainer;
   CP: IConnectionPoint;
begin
   Connection := 0;
   if Succeeded(Source.QueryInterface(IConnectionPointContainer, CPC)) then
     if Succeeded(CPC.FindConnectionPoint(IID, CP)) then
       CP.Advise(Sink, Connection);
end;


cely kod probehne spravne a do mista, kde se vola CP.Advise(Sink,
Connection), kde je Connection je cookie, ktery mam ziskat od Automation
objektu. U var. A je vse OK a cookie je 1, u var. B dostanu jako cookie 0.

Probuh, co ma kde nastavit, opravit, prepsat, upravit, ...., aby to
fungovalo. Su z toho jelen.


WinXP, D6PRO UPDATE PACK 2

kchodl

Odpovedá: Malecek Ondrej

24. 9. 2002 10:41

Abych pravdu rekl, framework okolo ConnectionPoints jsem zavrhl. Duvod:
univerzalni, ale bohuzel dost nepruhledne => neco na tom odladit chce pevne
nervy. Dale pri provozu po siti v tomto modelu naskakuji nezanedbatelne casy
pri volani metod jednotlivych callback rozhrani. Nakonec jsem skoncil u
toho, ze si velmi jednoduchou obsluhu realizuju sam, s tim, ze volani
kazdeho registrovaneho callback interfacu (Sink) mam delane ze specialniho
vlakna. Taky to ma samozrejme svoje => nelze pouzit pro vetsi mnozstvi
klientu (radove max. desitky), ale to mi staci. Vyhodou je, ze to ma clovek
docela pod kontrolou a volani callback interfacu mi bezi "paralelne", coz je
pro me dost podstatne.

O  

> -----Puvodni zprava-----
> No dobre, ale jak z toho ven. Pokracoval sem v pruzkumu a zameril
> sem se na
> klienta, ktery by v urcitem bode mel serveru predat ukazatel na
> sink event
> objekt a to procedurou InterfaceConnect, definovanou v jednotce
> ComObj.pas.
> Ta procedura vypada takto:
>
>
> procedure InterfaceConnect(const Source: IUnknown; const IID: TIID;
> const Sink: IUnknown; var Connection: Longint);
> var
> CPC: IConnectionPointContainer;
> CP: IConnectionPoint;
> begin
> Connection := 0;
> if Succeeded(Source.QueryInterface(IConnectionPointContainer,
> CPC)) then
> if Succeeded(CPC.FindConnectionPoint(IID, CP)) then
> CP.Advise(Sink, Connection);
> end;
>
>
> cely kod probehne spravne a do mista, kde se vola CP.Advise(Sink,
> Connection), kde je Connection je cookie, ktery mam ziskat od Automation
> objektu. U var. A je vse OK a cookie je 1, u var. B dostanu jako cookie 0.
>
> Probuh, co ma kde nastavit, opravit, prepsat, upravit, ...., aby to
> fungovalo. Su z toho jelen.
>
>
> WinXP, D6PRO UPDATE PACK 2
>
> kchodl
>

Odpovedá: Richard Kejval

27. 9. 2002 7:03

> Abych pravdu rekl, framework okolo ConnectionPoints jsem zavrhl. Duvod:
> univerzalni, ale bohuzel dost nepruhledne => neco na tom odladit chce
pevne
> nervy. Dale pri provozu po siti v tomto modelu naskakuji nezanedbatelne
casy
> pri volani metod jednotlivych callback rozhrani. Nakonec jsem skoncil u
> toho, ze si velmi jednoduchou obsluhu realizuju sam, s tim, ze volani
> kazdeho registrovaneho callback interfacu (Sink) mam delane ze specialniho
> vlakna. Taky to ma samozrejme svoje => nelze pouzit pro vetsi mnozstvi
> klientu (radove max. desitky), ale to mi staci. Vyhodou je, ze to ma
clovek
> docela pod kontrolou a volani callback interfacu mi bezi "paralelne", coz
je
> pro me dost podstatne.

S ConnectionPoints mam take dost spatne zkusenosti, nemohl bys to upresnit
na nejakem kratickem prikladku nebo mi to poslat na soukromy mail
kejvalr@volny.cz

Diky za ochotu

S pozdravem
Richard Kejval

Odpovedá: Malecek Ondrej

27. 9. 2002 12:57

No, abych byl presny, puvodne se tu mluvilo o ConnectionPoints pri pouziti
pres DCOM. V takovem tom "klasickem" pojeti (napr. event system, ktery ti
generuje wizard Delphi pro ActiveX komponentu) asi ConnectionPoints vyhovi.
Tu drive zminovanou obdobu pouzivam tedy v pripadech, kdy se mi
ConnectionPoints zdaji "ne uplne vhodne".

Napr:
  TClientCallbackItem = class( TThread)
    m_Callback : IClientCallback;
    procedure MakeCallbackCall;
    Procedure Execute;
  end;

  TServer = classs( ..., IServer)
  private
    procedure NotifyCallbacks;
    m_CallbackList : TList;
  public
     procedure RegisterCallback( IClientCallback);
  end;

procedure TServer.RegisterCallback( extCallback);
var m_Callback : TClientCallbackItem;
begin
  m_Callback := TClientCallbackItem.Create( extCallback);
end;

procedure TServer.NotifyCallbacks;
var intI;
begin
  for intI := 0 to ( m_CallbackList.Items.Count - 1) do
  begin
      TClientCallbackItem( m_CallbackList.Items[i]).MakeCallbackCall;
  end;
end;

procedure TClientCallbackItem.MakeCallbackCall;
begin
  PostThreadMessage( ThreadID, MSG_MAKE_CALLBACK_CALL, 0, 0);
end;

procedure TClientCallbackItem.Execute
begin
  while GetMessage( msg) do
  begin
    case msg.message of
      MSG_MAKE_CALLBACK_CALL:
      begin
        m_Callback.Notify;
      end;
    end;
  end;
end;

Vyse uvedene nutno brat pouze jako prikladek (urcite to nepujde prelozit
 . Pri volani
IServer.RegisterCallback se preda rozhrani, ktere ma server volat pri vzniku
udalosti (samozrejme je potreba uvnitr volat QueryInterface a takove ty
opicarny) Vytvori se instance TThread (TClientCallbackItem), ktera bude
obsluhovat volani (pozor na marshalling mezi apartmentem, kde se instance
vytvari a apartmentem toho threadu, ktery pak bude obsluhovat volani) a
zaradi se do seznamu registrovanych callbacku. Jak se provadi volani
callbacku pri vzniku udalosti je uz asi zrejme. Vlastni volani metod
IClientCallback je pak provadeno vzdy v k tomu ucelu vytvorenemu threadu =>
zejmena na siti se jednotliva volani mezi sebou neovlivnuji.

O  

> -----Původní zpráva-----
> S ConnectionPoints mam take dost spatne zkusenosti, nemohl bys to upresnit
> na nejakem kratickem prikladku